﻿using System;
using System.Text.RegularExpressions;
using WF_AppFuel.Base.Exceptions;
using WF_AppFuel.Base.Helpers;

namespace WF_AppFuel.Validation
{
    /// <summary>
    /// Utility class containing static 
    /// methods for common validation purposes.
    /// </summary>
    /// <author>Arjay C. Nacion</author>
    public static class ValidationUtil
    {
        /// <summary>
        /// Checks if the given string is empty.
        /// This method just delegates the action to
        /// System.String.IsNullOrEmpty() method
        /// </summary>
        /// <param name="str">The string to validate</param>
        /// <returns>True if empty, false otherwise.</returns>
        public static bool IsEmptyString(string str)
        {
            return string.IsNullOrEmpty(str);
        }

        /// <summary>
        /// Checks if the given string is empty or contains only whitespace.
        /// </summary>
        /// <param name="str">The string to validate</param>
        /// <returns>True if empty or whitespace, false otherwise.</returns>
        public static bool IsEmptyOrWhiteSpace(string str)
        {
            if (str == null)
                return true;

            return string.IsNullOrEmpty(str.Trim());
        }

        /// <summary>
        /// Checks if the given string can be parsed as a numeric value.
        /// </summary>
        /// <param name="str">The string to validate</param>
        /// <returns>True if numeric, false otherwise</returns>
        public static bool IsNumeric(string str)
        {
            double doubleValue = 0.00;
            if (double.TryParse(str, out doubleValue))
            {
                return true;
            }
            else
            {
                decimal decimalValue = 0M;
                if (decimal.TryParse(str, out decimalValue))
                    return true;
            }
            return false;
        }

        public static bool IsLessThan(byte value, byte checkValue)
        {
            return CheckLessThan(value, checkValue);
        }

        public static bool IsLessThan(short value, short checkValue)
        {
            return CheckLessThan(value, checkValue);
        }

        public static bool IsLessThan(int value, int checkValue)
        {
            return CheckLessThan(value, checkValue);
        }

        public static bool IsLessThan(long value, long checkValue)
        {
            return CheckLessThan(value, checkValue);
        }

        public static bool IsLessThan(double value, double checkValue)
        {
            return CheckLessThan(value, checkValue);
        }

        public static bool IsLessThan(decimal value, decimal checkValue)
        {
            return CheckLessThan(value, checkValue);
        }

        public static bool IsLessThan(DateTime value, DateTime checkValue)
        {
            return CheckLessThan(value, checkValue);
        }

        public static bool IsLessThan(string value, string checkValue)
        {
            return CheckLessThan(value, checkValue);
        }

        public static bool IsLessThan(string value, string checkValue, bool ignoreCase)
        {
            if (ignoreCase)
                return string.Compare(value, checkValue, StringComparison.OrdinalIgnoreCase) < 0;

            return IsLessThan(value, checkValue);
        }

        public static bool IsGreaterThan(byte value, byte checkValue)
        {
            return CheckGreaterThan(value, checkValue);
        }

        public static bool IsGreaterThan(short value, short checkValue)
        {
            return CheckGreaterThan(value, checkValue);
        }

        public static bool IsGreaterThan(int value, int checkValue)
        {
            return CheckGreaterThan(value, checkValue);
        }

        public static bool IsGreaterThan(long value, long checkValue)
        {
            return CheckGreaterThan(value, checkValue);
        }

        public static bool IsGreaterThan(double value, double checkValue)
        {
            return CheckGreaterThan(value, checkValue);
        }

        public static bool IsGreaterThan(decimal value, decimal checkValue)
        {
            return CheckGreaterThan(value, checkValue);
        }

        public static bool IsGreaterThan(DateTime value, DateTime checkValue)
        {
            return CheckGreaterThan(value, checkValue);
        }

        public static bool IsGreaterThan(string value, string checkValue)
        {
            return CheckGreaterThan(value, checkValue);
        }

        public static bool IsGreaterThan(string value, string checkValue, bool ignoreCase)
        {
            return string.Compare(value, checkValue, StringComparison.OrdinalIgnoreCase) > 0;
        }

        public static bool IsBetween(byte value, byte lower, byte upper)
        {
            return CheckBetween(value, lower, upper);
        }

        public static bool IsBetween(short value, short lower, short upper)
        {
            return CheckBetween(value, lower, upper);
        }

        public static bool IsBetween(int value, int lower, int upper)
        {
            return CheckBetween(value, lower, upper);
        }

        public static bool IsBetween(long value, long lower, long upper)
        {
            return CheckBetween(value, lower, upper);
        }

        public static bool IsBetween(double value, double lower, double upper)
        {
            return CheckBetween(value, lower, upper);
        }

        public static bool IsBetween(decimal value, decimal lower, decimal upper)
        {
            return CheckBetween(value, lower, upper);
        }

        /// <summary>
        /// Checks if the given DateTime <code>value</code> 
        /// is between <code>lower</code> and <code>upper</code>.
        /// </summary>
        /// <param name="value">The DateTime instance to check</param>
        /// <param name="lower">The lower bound</param>
        /// <param name="upper">The upper bound</param>
        /// <returns></returns>
        public static bool IsBetween(DateTime value, DateTime lower, DateTime upper)
        {
            return CheckBetween(value, lower, upper);
        }

        /// <summary>
        /// Checks if the given string <code>str</code> contains letters only
        /// or with whitespaces if <code>allowWhiteSpace</code> is set to true.
        /// </summary>
        /// <param name="str">The string to check</param>
        /// <param name="allowWhiteSpace">Indicates if whitespaces are allowed</param>
        /// <returns>True if alpha, false otherwise.</returns>
        public static bool IsAlpha(string str, bool allowWhiteSpace)
        {
            if (str == null)
                throw new InvalidArgumentException("String 'str' should not be null.");

            foreach (char c in str)
            {
                if (allowWhiteSpace && char.IsWhiteSpace(c))
                    continue;

                if (!char.IsLetter(c))
                    return false;
            }
            return true;
        }

        /// <summary>
        /// Checks if the given string <code>str</code> contains letters and numbers only 
        /// or with whitespaces if <code>allowWhiteSpace</code> is set to true.
        /// </summary>
        /// <param name="str">The string to check</param>
        /// <param name="allowWhiteSpace">Indicates if whitespaces are allowed</param>
        /// <exception cref="WF_AppFuel.Base.Exceptions.InvalidArgumentException">
        /// Thrown if the argument <code>str</code> is null.
        /// </exception>
        /// <returns>True if alphanumeric, false otherwise</returns>
        public static bool IsAlphaNumeric(string str, bool allowWhiteSpace)
        {
            if (str == null)
                throw new InvalidArgumentException("String 'str' should not be null.");

            foreach (char c in str)
            {
                if (allowWhiteSpace && char.IsWhiteSpace(c))
                    continue;

                if (!char.IsLetter(c) ||
                    !char.IsNumber(c))
                    return false;
            }
            return true;
        }

        /// <summary>
        /// Checks if the given string is a valid e-mail address.
        /// </summary>
        /// <param name="str">The string to check</param>
        /// <exception cref="WF_AppFuel.Base.Exceptions.InvalidArgumentException">
        /// Thrown if the argument <code>str</code> is null.
        /// </exception>
        /// <returns>True if valid email, false otherwise</returns>
        public static bool IsEmail(string str)
        {
            if (str == null)
                throw new InvalidArgumentException("Argument 'str' should not be null");

            Regex emailRegex = new Regex(RegexUtil.EMAIL_PATTERN);
            return emailRegex.IsMatch(str);
        }

        /// <summary>
        /// Checks if the given string is a valid IP address.
        /// </summary>
        /// <param name="str">The sting to check</param>
        /// <returns>True if valid IP Address, false otherwise</returns>
        public static bool IsIPAddress(string str)
        {
            Regex ipRegex = new Regex(RegexUtil.IP_ADDRESS_PATTERN);
            return ipRegex.IsMatch(str);
        }

        /// <summary>
        /// Checks if the given string <code>str</code> is a valid
        /// date, time or both.
        /// </summary>
        /// <param name="str">The string to check</param>
        /// <returns>True on validation success, false otherwise.</returns>
        public static bool IsDateTime(string str)
        {
            Regex dateRegex = new Regex(str);
            if (!dateRegex.IsMatch(str))
            {
                DateTime temp;
                if (!DateTime.TryParse(str, out temp))
                    return false;
            }

            return true;
        }

        /// <summary>
        /// Checks if the given string <code>value</code> matches the <code>pattern</code>.
        /// </summary>
        /// <param name="value">The string to check</param>
        /// <param name="pattern">The regular expression string pattern to be used.</param>
        /// <returns>True if value matches pattern, false otherwise</returns>
        public static bool MatchesPattern(string value, string pattern)
        {
            Regex regex = new Regex(pattern);
            return regex.IsMatch(value);
        }

        #region Private Helper Methods

        //Checks if the two objects are of the same type
        private static bool HasTheSameType(object obj1, object obj2)
        {
            return obj1.GetType().Equals(obj2.GetType());
        }

        //Helper method for comparing two objects
        private static int CompareObjects(object obj1, object obj2)
        {
            if (obj1 is byte && obj2 is byte)
                return ((byte)obj1).CompareTo((byte)obj2);
            else if (obj1 is short && obj2 is short)
                return ((short)obj1).CompareTo((short)obj2);
            else if (obj1 is int && obj2 is int)
                return ((int)obj1).CompareTo((int)obj2);
            else if (obj1 is long && obj2 is long)
                return ((long)obj1).CompareTo((long)obj2);
            else if (obj1 is double && obj2 is double)
                return ((double)obj1).CompareTo((double)obj2);
            else if (obj1 is decimal && obj2 is decimal)
                return ((decimal)obj1).CompareTo((decimal)obj2);
            else if (obj1 is DateTime && obj2 is DateTime)
                return ((DateTime)obj1).CompareTo((DateTime)obj2);
            else
                return string.Compare((string)obj1, (string)obj2);
        }

        /*
         * Helper method to check if the given value is less than the checkValue.
         * This method delegates the action to CompareObjects method.
         */
        private static bool CheckLessThan(object value, object checkValue)
        {
            return CompareObjects(value, checkValue) < 0;
        }

        /*
         * Helper method to check if the given value is greater than the checkValue.
         * This method delegates the action to CompareObjects method.
         */
        private static bool CheckGreaterThan(object value, object checkValue)
        {
            return CompareObjects(value, checkValue) > 0;
        }

        /**
         * Helper method to check if the given value is between the lower and upper bounds specified.
         * This method uses the Range helper class.
         */
        private static bool CheckBetween(object value, object lower, object upper)
        {
            if (value is byte)
                return new Range<byte>((byte)lower, (byte)upper)
                    .Contains((byte)value);
            else if (value is short)
                return new Range<short>((short)lower, (short)upper)
                    .Contains((short)value);
            else if (value is int)
                return new Range<int>((int)lower, (int)upper)
                    .Contains((int)value);
            else if (value is long)
                return new Range<long>((long)lower, (long)upper)
                    .Contains((long)value);
            else if (value is double)
                return new Range<double>((double)lower, (double)upper)
                    .Contains((double)value);
            else if (value is decimal)
                return new Range<decimal>((decimal)lower, (decimal)upper)
                    .Contains((decimal)value);
            else if (value is DateTime)
                return new Range<DateTime>((DateTime)lower, (DateTime)upper)
                    .Contains((DateTime)value);
            else if (value is string)
                return new Range<string>((string)lower, (string)upper)
                    .Contains((string)value);
            return false;
        }

        #endregion
    }
}
